/*
 Daniel Deutsch
 Max Lee
 Alexander Long
*/

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Scanner;

public class MemoryDriver {

	private static Date date;

	public static void main(String args[]) throws IOException, InterruptedException
	{
		String fileName = "p5.inlong.txt";									//The file to be read from
		File inFile = new File(fileName);
		Scanner fileScanner = new Scanner(inFile);				

		if (fileScanner.hasNext()) {										//Driver procedes if file has at least 1 line
			PrintWriter logWrite = new PrintWriter("p5translog.txt");		//File to write the log to
			PrintWriter analysisWrite = new PrintWriter("p5analysis.txt");	//File to write the analysis to

			int totalMem = Integer.valueOf(fileScanner.nextLine());			
			printLogHeader(logWrite, totalMem, fileName);					//Writes the log header to the file
			printAnalysisHeader(analysisWrite, totalMem, fileName);			//Writes the analysis header to the file

			MemoryTree tree = new MemoryTree(totalMem);						
			HashTable hash = new HashTable(totalMem);						
			Heap heap = new Heap(totalMem);
			
			//Statistics objects to hold relevant data for each of the implementations
			Statistics treeStat = new Statistics(16);
			Statistics hashStat = new Statistics(16);
			Statistics heapStat = new Statistics(16);
			
			//loop that will read and perform the instruction in the file
			while(fileScanner.hasNext())	
			{
				String line = fileScanner.nextLine();
				String[] lineArray = line.split("\\s++");

				//allocates to each Data Strucure
				if (lineArray[0].equals("A")){
					malloc(tree, lineArray[1], treeStat);
					malloc(hash, lineArray[1], hashStat);
					malloc(heap, lineArray[1], heapStat);
				}
				//Deallocates from each structure
				else {
					free(tree, lineArray[1], treeStat);
					free(hash, lineArray[1], hashStat);
					free(heap, lineArray[1], heapStat);
				}

			}
			
			printLog(logWrite, treeStat, hashStat, heapStat);			//prints the rest of the transaction log
			printAnalysis(analysisWrite, treeStat, hashStat, heapStat);	//prints the analysis data to the file
			System.out.println("done");

			logWrite.close();		//log writing complete
			analysisWrite.close();	//analysis writing complete
		}
	}
	
    /** Attempts to allocate memory of the specified size. Updates Statistics with the status if successful,
     * otherwise performs a sort and defrag. Performs a final allocation and updates Statistics with all the relevant data. 
     * @param manager	MemoryManager to allocate from
     * @param size		Amount of Memory to Allocate
     * @param stats		Statistics File of the MemoryManager 
     * @throws InterruptedException */
	public static void malloc(MemoryManager manager, String size, Statistics stats) throws InterruptedException
	{
		date = new Date();
		double mallocDuration, mallocStart = date.getTime();			//starts timing the allocation
		String status[] = manager.allocate(Integer.valueOf(size)).split(" ");	//performs the allocation
		Thread.sleep(200);
		date = new Date();
		mallocDuration = date.getTime() - mallocStart;	//stops timing the allocation

		if (Integer.valueOf(status[1]) >= 0) {
			stats.addTime(mallocDuration);
			stats.addStatus("A " + status[0] + " SUCCESS " + status[1]);	//allocation was successful
		}
		//allocation failed
		else {			
			sort(manager, stats);	//sort the free memory
			manager.defrag();		//defrag the free memory
			stats.addDefrag();		//adds a defrag to the stats

			date = new Date();
			mallocStart = date.getTime();	//starts timing again
			status = manager.allocate(Integer.valueOf(size)).split(" ");	//tries to allocate again
			Thread.sleep(200);
			date = new Date();
			mallocDuration = date.getTime() - mallocStart + mallocDuration;	//adds first time to the current time
			stats.addTime(mallocDuration);	//passes the time to the stats

			if (Integer.valueOf(status[1]) >= 0) {				
				stats.addStatus("A " + status[0] + " DF SUCCESS " + status[1]);	//allocation successful after defrag
			}
			else {
				stats.addSize(Integer.valueOf(size));
				stats.addStatus("A " + status[0] + " DF FAILED");			//allocation failed after defrag
			}
		}
		stats.next();	//advance the stats log
	}	
 
	/** Attempts to free the Memory of the specified UID 
	 * @param manager MemoryManager to free from
	 * @param UID The UID of the Memory
	 * @param stats Statistics file of the MemoryManager */
	public static void free(MemoryManager manager, String UID, Statistics stats)
	{
		String status = manager.deallocate(Integer.valueOf(UID));	//frees the Memory with the specified UID
		stats.addStatus("D " + UID + " " + status);	//passes the status to the log
		stats.next();	//advances the stats log

	}
    
	/** Tries to sort the specified MemoryManager. Does a Quicksort on a copy then BucketSorts 
	 * @param manager the MemoryManager to Sort
	 * @param stats Statistics file for that MemoryManager 
	 * @throws InterruptedException */
	private static void sort(MemoryManager manager, Statistics stats) throws InterruptedException
	{
		manager.sort(stats);	//performs a sort on the free memory
	}
	
	
	/** Prints the Header of the Log File 
	 * @param outFile the file to write to
	 * @param memory	//the total size of the Memory
	 * @param fileName	//the file being read from  */
	private static void printLogHeader(PrintWriter outFile, int memory, String fileName)
	{
		outFile.println("Memory Transactions Log\n");
		outFile.println("Memory Size: " + memory + " Input File Used: " + fileName + "\n");
		outFile.println("Memory Address begins at location 0\n");
		outFile.println("----------------------------------------------------------------------------\n");
		outFile.println("     Best Fit          Close Fit           Worse Fit  \n");
		outFile.println("----------------------------------------------------------------------------\n");
	}
	
	/** Prints the log file line by line  
	 * @param outFile the file to write to
	 * @param tree stats for the MemoryTree
	 * @param hash stats for the HashTable
	 * @param heap stats for the Heap */
	private static void printLog(PrintWriter outFile, Statistics tree, Statistics hash, Statistics heap)
	{
		for (int i = 0; i < tree.log.length; i++)
		{
			if (tree.log[i] == null)
				break;
			String s = String.format("%-20s%-20s%-20s", tree.log[i], hash.log[i], heap.log[i]); //creates the formated line containing the statuses
			outFile.println(s);	//prints the line
		}
	}

	/** Prints the header for the analysis file 
	 * @param outFile	the file to write to
	 * @param memory	the total size of the memory
	 * @param fileName	the file being read from */
	private static void printAnalysisHeader(PrintWriter outFile, int memory, String fileName)
	{
		outFile.println("Performance Analysis Chart");
		outFile.println("Memory Size: " + memory);
		outFile.println("Input File Used: " + fileName);
		outFile.println("----------------------------------------------------------------------------------");
		outFile.println("Statistic:                          Best Fit     Close Fit       Worst Fit");
		outFile.println("----------------------------------------------------------------------------------");
		outFile.println();
	}

	/** Prints the Statistics data for each Data Structure 
	 * @param outFile the file to write to
	 * @param tree Statistics file for the MemoryTree
	 * @param hash Statistics file for the HashTable
	 * @param heap Statistics file for the Heap */
	private static void printAnalysis(PrintWriter outFile, Statistics tree, Statistics hash, Statistics heap)
	{
		outFile.println(String.format("Number of defragmentations:       %8d%12d%16d", tree.numDefrag, hash.numDefrag , heap.numDefrag));
		outFile.println(String.format("# of failed allocation requests:  %8d%12d%16d", tree.numFailMalloc, hash.numFailMalloc, heap.numFailMalloc));
		outFile.println(String.format("Average size failed allocs:       %8d%12d%16d", tree.avgSizeFail(), hash.avgSizeFail(), heap.avgSizeFail()));
		outFile.println(String.format("Average time to process alloc:    %8.2f%12.2f%16.2f", tree.avgMalloc(), hash.avgMalloc(), heap.avgMalloc()));
		outFile.println(String.format("Average time/size quicksort:      %8.2f%12.2f%16.2f", tree.avgQuick(), hash.avgQuick(), heap.avgQuick()));
		outFile.println(String.format("Average time/size bucketsort:     %8.2f%12.2f%16.2f", tree.avgBucket(), hash.avgBucket(), heap.avgBucket()));
	}
}
